package com.wdl.datarest.implementation;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetAddress;
import java.util.HashSet;
import java.net.DatagramSocket;
import java.net.DatagramPacket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.boot.ApplicationArguments;

/**
 * This is the interface to device. It monitors 10166 port, receives
 * connection/data from each device, then responds accordingly.
 * This task does not process the received data much so as to respond device quickly.
 * It simply put the data into the queue of the DevWorkerThread.
 * DevWorkerThread will handle the data in details.
 */
@Component
@Order(3)
public class DevMonTask implements ApplicationRunner {
    private static Logger log = LoggerFactory.getLogger("DevMonTask");

    // At most 100000 devices, One thread handles 100000/10 = 10000 device.
    // Assume every 5 seconds one device reports once, 10000/5 = 2000fps
    // The monitor socket data speed is: 100000/5 = 20000fps
    private final int DEV_DATA_THREAD_NUM = 20;
    private DevWorkerThread[] devDataThreads = new DevWorkerThread[DEV_DATA_THREAD_NUM];

    private final int DEV_SU_THREAD_NUM = 10;
    private DevSUThread[] devSUThreads = new DevSUThread[DEV_SU_THREAD_NUM];
    private static HashSet<String> devInSU = new HashSet<>();

    @Value("${hst.logdata.receive}")
    boolean logReceivedDeviceData = true;

    @Value("${hst.logdata.send}")
    boolean logSentDeviceData = false;

    @Autowired
    private DevCnfgCache devCnfgCache;

    /*
     * Not sure how to autowire an Array of objects with initial value (threadId) yet.
     * Use an ugly workaround here for now:
     * - auto wire some threads
     * - add them into an array for load balance
     */
    @Autowired
    private DevWorkerThread devDataThread0;

    @Autowired
    private DevWorkerThread devDataThread1;

    @Autowired
    private DevWorkerThread devDataThread2;

    @Autowired
    private DevWorkerThread devDataThread3;

    @Autowired
    private DevWorkerThread devDataThread4;

    @Autowired
    private DevWorkerThread devDataThread5;

    @Autowired
    private DevWorkerThread devDataThread6;

    @Autowired
    private DevWorkerThread devDataThread7;

    @Autowired
    private DevWorkerThread devDataThread8;

    @Autowired
    private DevWorkerThread devDataThread9;

    @Autowired
    private DevWorkerThread devDataThread10;

    @Autowired
    private DevWorkerThread devDataThread11;

    @Autowired
    private DevWorkerThread devDataThread12;

    @Autowired
    private DevWorkerThread devDataThread13;

    @Autowired
    private DevWorkerThread devDataThread14;

    @Autowired
    private DevWorkerThread devDataThread15;

    @Autowired
    private DevWorkerThread devDataThread16;

    @Autowired
    private DevWorkerThread devDataThread17;

    @Autowired
    private DevWorkerThread devDataThread18;

    @Autowired
    private DevWorkerThread devDataThread19;

    @Autowired
    private DevSUThread devSUThread0;

    @Autowired
    private DevSUThread devSUThread1;

    @Autowired
    private DevSUThread devSUThread2;

    @Autowired
    private DevSUThread devSUThread3;

    @Autowired
    private DevSUThread devSUThread4;

    @Autowired
    private DevSUThread devSUThread5;

    @Autowired
    private DevSUThread devSUThread6;

    @Autowired
    private DevSUThread devSUThread7;

    @Autowired
    private DevSUThread devSUThread8;

    @Autowired
    private DevSUThread devSUThread9;

    private final int PORT_NUMBER = 10166;
    static public DatagramSocket serverSocket = null;

    // device software update variables
    // For full functionality mat
    private static String imageName = "00010001_WWU.bin";
    private static byte[] imageVersion = {0x0, 0x1, 0x0, 0x1};
    private static byte[] imageCRC = new byte[2];
    private static byte[] imageData = new byte[60 * 1024]; // Assume image size is less than 60K
    private static int intSize = 0;
    private static int numFrame = 0;

    // For wet detect mat
    private static String imageWetName = "00010001_WWU.bin";
    private static byte[] imageWetVersion = {0x0, 0x1, 0x0, 0x1};
    private static byte[] imageWetCRC = new byte[2];
    private static byte[] imageWetData = new byte[60 * 1024]; // Assume image size is less than 60K
    private static int intWetSize = 0;
    private static int numWetFrame = 0;

    public void run(ApplicationArguments args) {
        log.info("Starting device monitor task at port: " + PORT_NUMBER);
        int threadId;

        devDataThread0.setThreadId(0);
        devDataThreads[0] = devDataThread0;
        devDataThreads[0].start();

        devDataThread1.setThreadId(1);
        devDataThreads[1] = devDataThread1;
        devDataThreads[1].start();

        devDataThread2.setThreadId(2);
        devDataThreads[2] = devDataThread2;
        devDataThreads[2].start();

        devDataThread3.setThreadId(3);
        devDataThreads[3] = devDataThread3;
        devDataThreads[3].start();

        devDataThread4.setThreadId(4);
        devDataThreads[4] = devDataThread4;
        devDataThreads[4].start();

        devDataThread5.setThreadId(5);
        devDataThreads[5] = devDataThread5;
        devDataThreads[5].start();

        devDataThread6.setThreadId(6);
        devDataThreads[6] = devDataThread6;
        devDataThreads[6].start();

        devDataThread7.setThreadId(7);
        devDataThreads[7] = devDataThread7;
        devDataThreads[7].start();

        devDataThread8.setThreadId(8);
        devDataThreads[8] = devDataThread8;
        devDataThreads[8].start();

        devDataThread9.setThreadId(9);
        devDataThreads[9] = devDataThread9;
        devDataThreads[9].start();

        devDataThread10.setThreadId(10);
        devDataThreads[10] = devDataThread10;
        devDataThreads[10].start();

        devDataThread11.setThreadId(11);
        devDataThreads[11] = devDataThread11;
        devDataThreads[11].start();

        devDataThread12.setThreadId(12);
        devDataThreads[12] = devDataThread12;
        devDataThreads[12].start();

        devDataThread13.setThreadId(13);
        devDataThreads[13] = devDataThread13;
        devDataThreads[13].start();

        devDataThread14.setThreadId(14);
        devDataThreads[14] = devDataThread14;
        devDataThreads[14].start();

        devDataThread15.setThreadId(15);
        devDataThreads[15] = devDataThread15;
        devDataThreads[15].start();

        devDataThread16.setThreadId(16);
        devDataThreads[16] = devDataThread16;
        devDataThreads[16].start();

        devDataThread17.setThreadId(17);
        devDataThreads[17] = devDataThread17;
        devDataThreads[17].start();

        devDataThread18.setThreadId(18);
        devDataThreads[18] = devDataThread18;
        devDataThreads[18].start();

        devDataThread19.setThreadId(19);
        devDataThreads[19] = devDataThread19;
        devDataThreads[19].start();

        devSUThread0.setThreadId(0);
        devSUThreads[0] = devSUThread0;
        devSUThreads[0].start();

        devSUThread1.setThreadId(1);
        devSUThreads[1] = devSUThread1;
        devSUThreads[1].start();

        devSUThread2.setThreadId(2);
        devSUThreads[2] = devSUThread2;
        devSUThreads[2].start();

        devSUThread3.setThreadId(3);
        devSUThreads[3] = devSUThread3;
        devSUThreads[3].start();

        devSUThread4.setThreadId(4);
        devSUThreads[4] = devSUThread4;
        devSUThreads[4].start();

        devSUThread5.setThreadId(5);
        devSUThreads[5] = devSUThread5;
        devSUThreads[5].start();

        devSUThread6.setThreadId(6);
        devSUThreads[6] = devSUThread6;
        devSUThreads[6].start();

        devSUThread7.setThreadId(7);
        devSUThreads[7] = devSUThread7;
        devSUThreads[7].start();

        devSUThread8.setThreadId(8);
        devSUThreads[8] = devSUThread8;
        devSUThreads[8].start();

        devSUThread9.setThreadId(9);
        devSUThreads[9] = devSUThread9;
        devSUThreads[9].start();

        try {
            updateDeviceImageInfo();

            serverSocket = new DatagramSocket(PORT_NUMBER);
            while (true) {
                byte[] bytePacket = new byte[25];
                DatagramPacket packet = new DatagramPacket(bytePacket, bytePacket.length);

                /*
                 * Read data. Block till any received.
                 * Data structure:
* ---------------------------------------------------------------------------------------------------
* |byte    |  0  |  1  |  2  | 3  | 4 ~ 13 |  14  |  15     |  16        | 17  | 18  | 19~20 | 21   |
* ---------------------------------------------------------------------------------------------------
* |content |start|type |fNum |len |   ID   | HB/m |Breathe/m| SleepMode  |hbAlm|brAlm|  CRC  | end  |
* ---------------------------------------------------------------------------------------------------
* |value   |0xE2 |0xAA | 6   | 0xF| 10char | char |  char   | 0xB1/B2/B3 |0xA1 |0xA2 |MSB-LSB| 0xE3 |
* ---------------------------------------------------------------------------------------------------

* ---------------------------------------------------------------------------------
* |byte    | 0   |  1  | 2   | 3  | 4 ~ 13 |  14    |  15    | 16  | 17~18 | 19   |
* ---------------------------------------------------------------------------------
* |content |start|type |fNum |len |   ID   |  bed   |  move  | wet |  CRC  | end  |
* ---------------------------------------------------------------------------------
* |value   |0xE2 |0xBB | 7   | 0xD| 10char | 0xC0~4 | 0xc0/c5| 0xC6|MSB-LSB| 0xE3 |
* ---------------------------------------------------------------------------------
                 */

                serverSocket.receive(packet);
                byte[] tmp = packet.getData();

                byte[] byteDevId = new byte[10];
                for (int i = 0; i < 10; i++) {
                    byteDevId[i] = (byte) tmp[4 + i];
                }
                String devId = new String(byteDevId, 0, byteDevId.length);

                printPacketWithDirection(true, devId, tmp);
                if (log.isDebugEnabled()) {
                    log.debug("DevMonTask: Data received from " + devId + ", Data Type " + String.format("%02X", tmp[1]) + ": ");
                    printPacket(tmp);
                }

                // SU: For image download message, send image data in worker thread.
                if(tmp[1] != (byte)0xDB) {
                    sendResponseToDev(packet);
                }

                // SU: For image version message, or no SU is needed, nothing mroe to do, so not send to worker thread.
                if ((tmp[1] == (byte)0xDA) || (tmp[1] == 0xDB && tmp[14] != (byte)0x1)) {
                    if (tmp[1] == 0xDB && tmp[14] != (byte)0x1) {
                        removeDevInSU(devId);
                    }
                    continue;
                }

                synchronized (this) {
                    if (tmp[1] == (byte)0xDB) {
                        threadId = computeSUThreadId(devId);
                        if (log.isDebugEnabled()) {
                            log.debug("DevMonTask: Device ID " + devId + " matches to SU threadId " + threadId);
                        }
                        int waitTimes = 0;
                        while (devSUThreads[threadId].isQueueFull() && (waitTimes < 10)) {
                            log.warn("DevMonTask: devSUThread[" + threadId + "] is not processing SU normally, its queue is full.");
                            // Sleep 0.1s to see if this thread can recover
                            Thread.sleep(100);
                            waitTimes++;
                        }
                        if (devSUThreads[threadId].isQueueFull()) {
                            // If queue is still full after 1 second give up this data.
                            continue;
                        }
                        devSUThreads[threadId].add(packet);
                        continue;
                    }

                    threadId = computeThreadId(devId);
                    if (log.isDebugEnabled()) {
                        log.debug("DevMonTask: Device ID " + devId + " matches to threadId " + threadId);
                    }
                    int waitTimes = 0;
                    while (devDataThreads[threadId].isQueueFull() && (waitTimes < 10)) {
                        log.warn("DevMonTask: devDataThread[" + threadId + "] is not processing data normally, its queue is full.");
                        // Sleep 0.1s to see if this thread can recover
                        Thread.sleep(100);
                        waitTimes++;
                    }
                    if (devDataThreads[threadId].isQueueFull()) {
                        // If queue is still full after 1 second give up this data.
                        continue;
                    }
                    devDataThreads[threadId].add(packet);
                    if (tmp[1] == (byte)0xDB) {
                        continue;
                    }
                }
            }
        } catch (IOException e) {
            log.error("Failed to process package.", e);
        } catch (Exception e) {
            log.error("Failed to process package.", e);
        } finally {
            serverSocket.close();
        }
    }

    private void sendResponseToDev(DatagramPacket packet) {
        byte[] bytePacket = new byte[25];
        byte[] byteResponse = new byte[25];
        byte[] byteDevId = new byte[10];
        boolean syncedToDev = true;

        String strDevId = null;
        CacheData cacheData = null;

        InetAddress address = packet.getAddress();
        int port = packet.getPort();
        DatagramPacket responsePacket = null;

        bytePacket = packet.getData();

        // The device ID starts from 5th and 10 bytes long.
        for (int i = 0; i < 10; i++) {
            byteDevId[i] = bytePacket[4+i];
        }
        strDevId = new String(byteDevId, 0, byteDevId.length);

        // Handle device software update message
        if (bytePacket[1] == (byte)0xDA) {
            // DA1. Header: always 0xE5
            byteResponse[0] = (byte) 0xE5;
            // DA2. Type: 0xF1(config data), 0xF2(reserved), 0xF3(ACK), 0xF4(SU)
            byteResponse[1] = (byte)0xF4;
            // DA3. Frame number
            byteResponse[2] = bytePacket[2];
            // DA4. Data length
            byteResponse[3] = 3;
            // DA5. Data (0 or number of frame of the device image file)
            if (byteDevId[1] == (byte)0x31) {
                if (bytePacket[14] == imageVersion[0] &&
                    bytePacket[15] == imageVersion[1] &&
                    bytePacket[16] == imageVersion[2] &&
                    bytePacket[17] == imageVersion[3]) {
                    byteResponse[4] = 0; // 0 means no software update is needed
                    // DA6. device image CRC as 0 if no update
                    byteResponse[5] = 0;
                    byteResponse[6] = 0;
                } else {
                    byteResponse[4] = (byte)numFrame;
                    // DA6. device image CRC
                    byteResponse[5] = imageCRC[1];
                    byteResponse[6] = imageCRC[0];
                    // Indicate SU on going
                    addDevInSU(strDevId);
                }
            } else if (byteDevId[1] == (byte)0x35) {
                if (bytePacket[14] == imageWetVersion[0] &&
                    bytePacket[15] == imageWetVersion[1] &&
                    bytePacket[16] == imageWetVersion[2] &&
                    bytePacket[17] == imageWetVersion[3]) {
                    byteResponse[4] = 0; // 0 means no software update is needed
                    // DA6. device image CRC as 0 if no update
                    byteResponse[5] = 0;
                    byteResponse[6] = 0;
                } else {
                    byteResponse[4] = (byte)numWetFrame;
                    // DA6. device image CRC
                    byteResponse[5] = imageWetCRC[1];
                    byteResponse[6] = imageWetCRC[0];
                    // Indicate SU on going
                    addDevInSU(strDevId);
                }
            } else {
                log.info("Device type is not full functionality or wet: "+byteDevId[1]);
            }

            // DA7. this packet CRC
            byte[] byteCRCMsg = new byte[6];
            for (int i= 0; i< byteCRCMsg.length; i++) {
                byteCRCMsg[i] = byteResponse[i+1];
            }
            short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
            byte[] byteCRC = short2bytes(shortCRC);
            byteResponse[7] = byteCRC[1];
            byteResponse[8] = byteCRC[0];
            // DA8. Tail
            byteResponse[9] = (byte)0xE6;
        } else {
            cacheData = devCnfgCache.getCnfgDataFromCacheByDeviceIdString(strDevId);

            if (cacheData != null) {
                syncedToDev = cacheData.getSyncedToDev();
            }

            /*
             * When data is received, a response to device is always needed.
             * Response can be a simple ACK, or device config data.
             */
            // a) Header: always 0xE5
            byteResponse[0] = (byte) 0xE5;

            if (false == syncedToDev) {
                // b) Type: 0xF1(config data), 0xF2(reserved), 0xF3(ACK)
                byteResponse[1] = (byte)0xF1;
                // c) Frame number
                byteResponse[2] = bytePacket[2];
                // d) Data length
                byteResponse[3] = 7;
                // e) Data
                if (cacheData.getEnableUpAlarm() == true) {
                    byteResponse[4] = (byte)0xC1;
                } else {
                    byteResponse[4] = (byte)0x0;
                }
                if (cacheData.getEnableEdgeAlarm() == true) {
                    byteResponse[5] = (byte)0xC2;
                } else {
                    byteResponse[5] = (byte)0x0;
                }
                if (cacheData.getEnableAwayAlarm() == true) {
                    byteResponse[6] = (byte)0xC3;
                } else {
                    byteResponse[6] = (byte)0x0;
                }
                byteResponse[7] = (byte)cacheData.getHbUpperTH();
                byteResponse[8] = (byte)cacheData.getHbLowerTH();
                byteResponse[9] = (byte)cacheData.getBrUpperTH();
                byteResponse[10] = (byte)cacheData.getBrLowerTH();
                // f) CRC
                byte[] byteCRCMsg = new byte[10];
                for (int i= 0; i < byteCRCMsg.length; i++) {
                    byteCRCMsg[i] = byteResponse[i+1];
                }
                short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
                byte[] byteCRC = short2bytes(shortCRC);
                byteResponse[11] = byteCRC[1];
                byteResponse[12] = byteCRC[0];
                // g) Tail
                byteResponse[13] = (byte)0xE6;
            } else {
                // b) Type: 0xF1(config data), 0xF2(reserved), 0xF3(ACK)
                byteResponse[1] = (byte)0xF3;
                // c) Frame number
                byteResponse[2] = bytePacket[2];
                // d) Data length
                byteResponse[3] = 1;
                // e) Data
                byteResponse[4] = (byte)0xE4;
                // f) CRC
                byte[] byteCRCMsg = new byte[4];
                for (int i = 0; i< byteCRCMsg.length; i++) {
                    byteCRCMsg[i] = byteResponse[i+1];
                }
                short shortCRC = getCrc16(byteCRCMsg, byteCRCMsg.length);
                byte[] byteCRC = short2bytes(shortCRC);
                byteResponse[5] = byteCRC[1];
                byteResponse[6] = byteCRC[0];
                // g) Tail
                byteResponse[7] = (byte)0xE6;
            }

            // send the response to the device at "address" and "port"
            if (log.isDebugEnabled()) {
                log.debug("DevMonTask: Sending response to device " + strDevId + ": ");
                printPacket(byteResponse);
            }
            printPacketWithDirection(false, strDevId, byteResponse);
        }

        responsePacket = new DatagramPacket(byteResponse, byteResponse.length, address, port);

        try{
            serverSocket.send(responsePacket);
        } catch (IOException ioe) {
            log.error("DevMonTask: Failed to send response to device.", ioe);
            return;
        } catch (Exception e) {
            log.error("DevMonTask: Failed to send response to device.", e);
            return;
        }

        if (syncedToDev == false) {
            cacheData.setSyncedToDev(true);
            devCnfgCache.updateCacheByDeviceIdString(strDevId, cacheData);
        }
    }

    public static short getCrc16(byte[] Msg, int length) {
        int CRC = 0xffff;
        int wCPoly = 0x1021;
        int wChar = 0;
        int i = 0;

        while (i < length) {
            wChar = Msg[i];
            CRC ^= (wChar << 8);

            for (int count = 0; count < 8; count++) {
                if ((CRC & 0x8000) != 0) {
                    CRC = (CRC << 1) ^ wCPoly;
                } else {
                    CRC = CRC << 1;
                }
            }
            i++;
        }
        return (short) CRC;
    }

    public static byte[] short2bytes(short s) {
        byte[] bytes = new byte[2];
        for (int i = 0; i < 2; i++) {
            bytes[i] = (byte) (s % 256);
            s >>= 8;
        }
        return bytes;
    }

    private int computeThreadId(String devId) {
        // log.info("devId is: " + devId + ", devDataThreads.length is: " + devDataThreads.length + ", hash is: " + Math.abs(devId.hashCode()));
        // return Math.abs(devId.hashCode()%100) % devDataThreads.length;
        return Math.abs(devId.hashCode()) % devDataThreads.length;
    }

    private int computeSUThreadId(String devId) {
        return Math.abs(devId.hashCode()) % devSUThreads.length;
    }

    public static void printPacket(byte[] bArray) {
        int len = bArray.length;
        String strOutput = "";
        for (int i = 0; i < len; i++) {
            strOutput = strOutput + String.format("%02X ", bArray[i]);
        }
        log.info(strOutput);
    }

    public void printPacketWithDirection(boolean fromDevice, String deviceId, byte[] bArray) {
        if ((fromDevice && !logReceivedDeviceData) || (!fromDevice && !logSentDeviceData)) {
            return;
        }

        StringBuffer sb = new StringBuffer();
        if (fromDevice) {
            sb.append("[" + deviceId + "] --> ");
        } else {
            sb.append("[" + deviceId + "] <-- ");
        }

        int len = bArray.length;
        for (int i = 0; i < len; i++) {
            sb.append(String.format("%02X ", bArray[i]));
        }

        log.info(sb.toString());
    }

    /*
     * When a new device image is uploaded to server through web GUI this API should be called
     * to update these infomation of the new device image.
     * TODO: Need a RW lock in case a device triggers an SU when image is being updated?
     */
    public static void updateDeviceImageInfo() {
        // Load the device image for SU in cache
        File file = new File("/opt/hushitong/su/");
        File[] fileList = file.listFiles();
        String diskImageName = "";
        boolean imageFound = false;
        boolean imageWetFound = false;

        for (int i = 0; i < fileList.length; i++) {
            if (fileList[i].isFile()) {
                diskImageName = fileList[i].getName();
                if (diskImageName.startsWith("0001") && diskImageName.endsWith("_WWU.bin")) {
                    imageName = diskImageName;
                    imageFound = true;
                } else if (diskImageName.startsWith("0005") && diskImageName.endsWith("_GYT.bin")) {
                    imageWetName = diskImageName;
                    imageWetFound = true;
                } else {
                    log.info("image type not supported yet");
                }
            }
        }

        if (imageFound == false && imageWetFound == false) {
            log.info("Image for device type 0001 and 0005 is not found");
            return;
        }

        RandomAccessFile accessFile = null;
        try {
            if (imageFound == true) {
                accessFile = new RandomAccessFile("/opt/hushitong/su/" + imageName,"r");
                if ((intSize = accessFile.read(imageData, 0, imageData.length)) != -1) {
                    short shortCRC = getCrc16(imageData, intSize);
                    imageCRC = short2bytes(shortCRC);
                    numFrame = intSize/512 + ((intSize%512 == 0) ? 0 : 1);
                    imageVersion[0]= (byte)0x0; imageVersion[1]= (byte)0x1;
                    String strVersion = imageName.substring(4,8);
                    int intVersion = Integer.parseUnsignedInt(strVersion, 16);
                    imageVersion[2]= (byte)(intVersion >> 8);
                    imageVersion[3]= (byte)(intVersion & 0xFF);
                }
                log.info("Load device image " + imageName + " in cache, size is " + intSize + ", numFrame is " + numFrame +
                         ", imageCRC is " + String.format("%02X ",imageCRC[1]) + String.format("%02X ",imageCRC[0]) +
                         ", imageVersion is " + String.format("%02X ",imageVersion[0]) + String.format("%02X ",imageVersion[1]) +
                         String.format("%02X ",imageVersion[2]) + String.format("%02X ",imageVersion[3]));
            }

            if (imageWetFound == true) {
                accessFile = new RandomAccessFile("/opt/hushitong/su/" + imageWetName,"r");
                if ((intWetSize = accessFile.read(imageWetData, 0, imageWetData.length)) != -1) {
                    short shortCRC = getCrc16(imageWetData, intWetSize);
                    imageWetCRC = short2bytes(shortCRC);
                    numWetFrame = intWetSize/512 + ((intWetSize%512 == 0) ? 0 : 1);
                    imageWetVersion[0]= (byte)0x0; imageWetVersion[1]= (byte)0x5;
                    String strVersion = imageWetName.substring(4,8);
                    int intVersion = Integer.parseUnsignedInt(strVersion, 16);
                    imageWetVersion[2]= (byte)(intVersion >> 8);
                    imageWetVersion[3]= (byte)(intVersion & 0xFF);
                }

                log.info("Load device image " + imageWetName + " in cache, size is " + intWetSize + ", numWetFrame is " + numWetFrame +
                         ", imageWetCRC is " + String.format("%02X ",imageWetCRC[1]) + String.format("%02X ",imageWetCRC[0]) +
                         ", imageWetVersion is " + String.format("%02X ",imageWetVersion[0]) + String.format("%02X ",imageWetVersion[1]) +
                         String.format("%02X ",imageWetVersion[2]) + String.format("%02X ",imageWetVersion[3]));
            }
            //for (int i=0; i<10; i++) {
            //      log.info("d[" + i + "]=" + String.format("%02X ",imageData[i]));
            //}
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (accessFile != null) {
                    accessFile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static byte[] getImageData(byte devType) {
        if (devType == (byte)0x31) {
            return imageData;
        } else if (devType == (byte)0x35) {
            return imageWetData;
        }
        return imageData;
    }

    public static int getImageSize(byte devType) {
        if (devType == (byte)0x31) {
            return intSize;
        } else if (devType == (byte)0x35) {
            return intWetSize;
        }
        return intSize;
    }

    public static int getNumFrame(byte devType) {
        if (devType ==(byte)0x31) {
            return numFrame;
        } else if (devType == (byte)0x35) {
            return numWetFrame;
        }
        return numFrame;
    }

    public static void addDevInSU(String devId) {
        devInSU.add(devId);
    }

    public static void removeDevInSU(String devId) {
        devInSU.remove(devId);
    }

    public static boolean notDevInSU(String devId) {
        return (devInSU.contains(devId) != true);
    }
}